/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.jena.graph.test;

/**
    An extension of JenaTestBase (which see) with Graph-specific methods.
*/

import java.io.FileNotFoundException;
import java.lang.reflect.Constructor;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.*;

import org.apache.jena.graph.*;
import org.apache.jena.ontology.impl.TestListSyntaxCategories;
import org.apache.jena.shared.JenaException;
import org.apache.jena.shared.PrefixMapping;
import org.apache.jena.test.JenaTestBase;
import org.apache.jena.util.CollectionFactory;
import org.apache.jena.util.iterator.ExtendedIterator;

public class GraphTestBase extends JenaTestBase {
    protected static String getFileName(String fn) {
        URL u = TestListSyntaxCategories.class.getClassLoader().getResource(fn);
        if ( u == null ) {
            throw new RuntimeException(new FileNotFoundException(fn));
        }
        try {
            return u.toURI().toString();
        } catch (URISyntaxException e) {
            throw new RuntimeException(e);
        }
    }

    public GraphTestBase(String name) {
        super(name);
    }

    /**
     * Answer a Node as described by <code>x</code>; a shorthand for
     * <code>NodeCreateUtils.create(x)</code>, which see.
     */
    public static Node node(String x) {
        return NodeCreateUtils.create(x);
    }

// /**
// Answer a set containing the elements from the iterator <code>it</code>.
// */
// public static <T> Set<T> Iter.toSet( Iterator<? extends T> it )
// { return Iter.toSet( it ); }
//
// /**
// Answer a list containing the elements from the iterator <code>it</code>.
// */
// public static <T> List<T> Iter.toList( Iterator<? extends T> it )
// { return Iter.toList( it ); }

    /**
     * Answer a set of the nodes described (as per <code>node()</code>) by the
     * space-separated substrings of <code>nodes</code>.
     */
    public Set<Node> nodeSet(String nodes) {
        Set<Node> result = CollectionFactory.createHashedSet();
        StringTokenizer st = new StringTokenizer(nodes);
        while (st.hasMoreTokens())
            result.add(node(st.nextToken()));
        return result;
    }

    /**
     * Answer a set of the elements of <code>A</code>.
     */
    public <T> Set<T> arrayToSet(T[] A) {
        return CollectionFactory.createHashedSet(Arrays.asList(A));
    }

    /**
     * Answer a triple described by the three space-separated node descriptions in
     * <code>fact</code>; a shorthand for
     * <code>NodeCreateUtils.createTriple(fact)</code>, which see.
     */
    public static Triple triple(String fact) {
        return NodeCreateUtils.createTriple(fact);
    }

    /**
     * Answer a triple described by the three space-separated node descriptions in
     * <code>fact</code>, using prefix-mappings from <code>pm</code>; a shorthand for
     * <code>Triple.create(pm, fact)</code>, which see.
     */
    public static Triple triple(PrefixMapping pm, String fact) {
        return NodeCreateUtils.createTriple(pm, fact);
    }

    /**
     * Answer an array of triples; each triple is described by one of the
     * semi-separated substrings of <code>facts</code>, as per <code>triple</code>
     * with prefix-mapping <code>Extended</code>.
     */
    public static Triple[] tripleArray(String facts) {
        ArrayList<Triple> al = new ArrayList<>();
        StringTokenizer semis = new StringTokenizer(facts, ";");
        while (semis.hasMoreTokens())
            al.add(triple(PrefixMapping.Extended, semis.nextToken()));
        return al.toArray(new Triple[al.size()]);
    }

    /**
     * Answer a set of triples where the elements are described by the semi-separated
     * substrings of <code>facts</code>, as per <code>triple</code>.
     */
    public static Set<Triple> tripleSet(String facts) {
        Set<Triple> result = new HashSet<>();
        StringTokenizer semis = new StringTokenizer(facts, ";");
        while (semis.hasMoreTokens())
            result.add(triple(semis.nextToken()));
        return result;
    }

    /**
     * Answer a list of nodes, where the nodes are described by the space-separated
     * substrings of <code>items</code> as per <code>node()</code>.
     */
    public static List<Node> nodeList(String items) {
        ArrayList<Node> nl = new ArrayList<>();
        StringTokenizer nodes = new StringTokenizer(items);
        while (nodes.hasMoreTokens())
            nl.add(node(nodes.nextToken()));
        return nl;
    }

    /**
     * Answer an array of nodes, where the nodes are described by the space-separated
     * substrings of <code>items</code> as per
     */
    public static Node[] nodeArray(String items) {
        List<Node> nl = nodeList(items);
        return nl.toArray(new Node[nl.size()]);
    }

    /**
     * Answer the graph <code>g</code> after adding to it every triple encoded in
     * <code>s</code> in the fashion of <code>tripleArray</code>, a semi-separated
     * sequence of space-separated node descriptions.
     */
    public static Graph graphAdd(Graph g, String s) {
        StringTokenizer semis = new StringTokenizer(s, ";");
        while (semis.hasMoreTokens())
            g.add(triple(PrefixMapping.Extended, semis.nextToken()));
        return g;
    }

    /**
     * Answer a new memory-based graph with Extended prefixes.
     */
    public static Graph newGraph() {
        Graph result = GraphMemFactory.createDefaultGraphSameValue();
        result.getPrefixMapping().setNsPrefixes(PrefixMapping.Extended);
        return result;
    }

    /**
     * Answer a new memory-based graph with initial contents as described by
     * <code>s</code> in the fashion of <code>graphAdd()</code>. Not over-ridable; do
     * not use for abstraction.
     */
    public static Graph graphWith(String s) {
        return graphAdd(newGraph(), s);
    }

    /**
     * Assert that the graph <code>g</code> is isomorphic to the graph described by
     * <code>template</code> in the fashion of <code>graphWith</code>.
     */
    public static void assertEqualsTemplate(String title, Graph g, String template) {
        assertIsomorphic(title, graphWith(template), g);
    }

    /**
     * Assert that the supplied graph <code>got</code> is isomorphic with the the
     * desired graph <code>expected</code>; if not, display a readable description of
     * both graphs.
     */
    public static void assertIsomorphic(String title, Graph expected, Graph got) {
        if ( !expected.isIsomorphicWith(got) ) {
            Map<Node, Object> map = CollectionFactory.createHashedMap();
            fail(title + ": wanted " + nice(expected, map) + "\nbut got " + nice(got, map));
        }
    }

    /**
     * Answer a string which is a newline-separated list of triples (as produced by
     * niceTriple) in the graph <code>g</code>. The map <code>bnodes</code> maps
     * already-seen bnodes to their "nice" strings.
     */
    public static String nice(Graph g, Map<Node, Object> bnodes) {
        StringBuilder b = new StringBuilder(g.size() * 100);
        ExtendedIterator<Triple> it = GraphUtil.findAll(g);
        while (it.hasNext())
            niceTriple(b, bnodes, it.next());
        return b.toString();
    }

    /**
     * Append to the string buffer <code>b</code> a "nice" representation of the
     * triple <code>t</code> on a new line, using (and updating) <code>bnodes</code>
     * to supply "nice" strings for any blank nodes.
     */
    protected static void niceTriple(StringBuilder b, Map<Node, Object> bnodes, Triple t) {
        b.append("\n    ");
        appendNode(b, bnodes, t.getSubject());
        appendNode(b, bnodes, t.getPredicate());
        appendNode(b, bnodes, t.getObject());
    }

    /**
     * A counter for new bnode strings; it starts at 1000 so as to make the bnode
     * strings more uniform (at least for the first 9000 bnodes).
     */
    protected static int bnc = 1000;

    /**
     * Append to the string buffer <code>b</code> a space followed by the "nice"
     * representation of the node <code>n</code>. If <code>n</code> is a bnode,
     * re-use any existing string for it from <code>bnodes</code> or make a new one
     * of the form <i>_bNNNN</i> with NNNN a new integer.
     */
    protected static void appendNode(StringBuilder b, Map<Node, Object> bnodes, Node n) {
        b.append(' ');
        if ( n.isBlank() ) {
            Object already = bnodes.get(n);
            if ( already == null )
                bnodes.put(n, already = "_b" + bnc++);
            b.append(already);
        } else
            b.append(nice(n));
    }

    /**
     * Answer the "nice" representation of this node, the string returned by
     * <code>n.toString(PrefixMapping.Extended,true)</code>.
     */
    protected static String nice(Node n) {
        return n.toString(PrefixMapping.Extended);
    }

    /**
     * Assert that the computed graph <code>got</code> is isomorphic with the desired
     * graph <code>expected</code>; if not, fail with a default message (and pretty
     * output of the graphs).
     */
    public static void assertIsomorphic(Graph expected, Graph got) {
        assertIsomorphic("graphs must be isomorphic", expected, got);
    }

    /**
     * Assert that the graph <code>g</code> must contain the triple described by
     * <code>s</code>; if not, fail with pretty output of both graphs and a message
     * containing <code>name</code>.
     */
    public static void assertContains(String name, String s, Graph g) {
        assertTrue(name + " must contain " + s, g.contains(triple(s)));
    }

    /**
     * Assert that the graph <code>g</code> contains all the triples described by the
     * string <code>s</code; if not, fail with a message containing
     <code>name</code>.
     */
    public static void assertContainsAll(String name, Graph g, String s) {
        StringTokenizer semis = new StringTokenizer(s, ";");
        while (semis.hasMoreTokens())
            assertContains(name, semis.nextToken(), g);
    }

    /**
     * Assert that the graph <code>g</code> does not contain the triple described by
     * <code>s<code>; if it does, fail with a message containing
     <code>name</code>.
     */
    public static void assertOmits(String name, Graph g, String s) {
        assertFalse(name + " must not contain " + s, g.contains(triple(s)));
    }

    /**
     * Assert that the graph <code>g</code> contains none of the triples described by
     * <code>s</code> in the usual way; otherwise, fail with a message containing
     * <code>name</code>.
     */
    public static void assertOmitsAll(String name, Graph g, String s) {
        StringTokenizer semis = new StringTokenizer(s, ";");
        while (semis.hasMoreTokens())
            assertOmits(name, g, semis.nextToken());
    }

    /**
     * Assert that <code>g</code> contains the triple described by <code>fact</code>
     * in the usual way.
     */
    public static boolean contains(Graph g, String fact) {
        return g.contains(triple(fact));
    }

    /**
     * Assert that <code>g</code> contains every triple in <code>triples</code>.
     */
    public void testContains(Graph g, Triple[] triples) {
        for ( Triple triple : triples ) {
            assertTrue("contains " + triple, g.contains(triple));
        }
    }

    /**
     * Assert that <code>g</code> contains every triple in <code>triples</code>.
     */
    public void testContains(Graph g, List<Triple> triples) {
        for ( Triple triple : triples ) {
            assertTrue(g.contains(triple));
        }
    }

    /**
     * Assert that <code>g</code> contains every triple in <code>it</code>.
     */
    public void testContains(Graph g, Iterator<Triple> it) {
        while (it.hasNext())
            assertTrue(g.contains(it.next()));
    }

    /**
     * Assert that <code>g</code> contains every triple in <code>other</code>.
     */
    public void testContains(Graph g, Graph other) {
        testContains(g, GraphUtil.findAll(other));
    }

    /**
     * Assert that <code>g</code> contains none of the triples in
     * <code>triples</code>.
     */
    public void testOmits(Graph g, Triple[] triples) {
        for ( Triple triple : triples ) {
            assertFalse("", g.contains(triple));
        }
    }

    /**
     * Assert that <code>g</code> contains none of the triples in
     * <code>triples</code>.
     */
    public void testOmits(Graph g, List<Triple> triples) {
        for ( Triple triple : triples ) {
            assertFalse("", g.contains(triple));
        }
    }

    /**
     * Assert that <code>g</code> contains none of the triples in <code>it</code>.
     */
    public void testOmits(Graph g, Iterator<Triple> it) {
        while (it.hasNext())
            assertFalse("", g.contains(it.next()));
    }

    /**
     * Assert that <code>g</code> contains none of the triples in <code>other</code>.
     */
    public void testOmits(Graph g, Graph other) {
        testOmits(g, GraphUtil.findAll(other));
    }

    /**
     * Answer an instance of <code>graphClass</code>. If <code>graphClass</code> has
     * a constructor that takes a <code>ReificationStyle</code> argument, then that
     * constructor is run on <code>style</code> to get the instance. Otherwise, if it
     * has a # constructor that takes an argument of <code>wrap</code>'s class before
     * the <code>ReificationStyle</code>, that constructor is used; this allows
     * non-static inner classes to be used for <code>graphClass</code>, with
     * <code>wrap</code> being the outer class instance. If no suitable constructor
     * exists, a JenaException is thrown.
     *
     * @param wrap the outer class instance if graphClass is an inner class
     * @param graphClass a class implementing Graph
     * @return an instance of graphClass with the given style
     * @throws RuntimeException or JenaException if construction fails
     */
    public static Graph getGraph(Object wrap, Class<? extends Graph> graphClass) {
        try {
            Constructor<? > cons = getConstructor(graphClass, new Class[]{});
            if ( cons != null )
                return (Graph)cons.newInstance(new Object[]{});
            Constructor<? > cons2 = getConstructor(graphClass, new Class[]{wrap.getClass()});
            if ( cons2 != null )
                return (Graph)cons2.newInstance(new Object[]{wrap});
            throw new JenaException("no suitable graph constructor found for " + graphClass);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new JenaException(e);
        }
    }
}
